package com.querie.controller;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Random;
import java.util.TreeMap;

import com.querie.config.Configuration;
import com.querie.dao.DAOFactory;
import com.querie.dao.ModelDAO;
import com.querie.dao.QueryDAO;
import com.querie.model.Similarity;
import com.querie.model.beans.PrecisionRecallStatistics;
import com.querie.model.beans.Query;
import com.querie.model.beans.QueryLog;
import com.querie.model.beans.RunInfo;
import com.querie.model.beans.StatInfo;
import com.querie.sql.ConnectionFactory;
import com.querie.util.MetricsCalc;
import com.querie.util.RunDataExcelWriter;
import com.querie.util.SQLKeyword;
import com.querie.util.Util;

public class EvaluationController {

	static final String testInputDir = Configuration.getTestInputDir();
	static int numOfFolds = Configuration.getNumOfFolds();
	static int numOfTopFragments = Configuration.getkValForFragments();

	private static class SessionGroup {
		List<Long> lstSession;

		public SessionGroup(Long SessionId) {
			lstSession = new ArrayList<Long>();
			lstSession.add(SessionId);
		}

		public boolean addSession(Long SessionId) {
			return lstSession.add(SessionId);
		}

		public int getSize() {
			return lstSession.size();
		}

		public void remove() {
			lstSession.remove(new Random().nextInt(lstSession.size()));
		}

		public boolean hasNext() {
			return lstSession.size() > 0 ? true : false;
		}

		public long next() {
			return lstSession.remove(getSize() - 1);
		}
	}

	private static class QueryGroup {
		List<Long> lstQuery;

		public QueryGroup(Long queryId) {
			lstQuery = new ArrayList<Long>();
			lstQuery.add(queryId);
		}

		public boolean addQuery(Long queryId) {
			return lstQuery.add(queryId);
		}

		public int getSize() {
			return lstQuery.size();
		}

		public void remove() {
			lstQuery.remove(new Random().nextInt(lstQuery.size()));

		}

		public boolean hasNext() {
			return lstQuery.size() > 0 ? true : false;
		}

		public long next() {
			return lstQuery.remove(getSize() - 1);
		}

		public List<Long> getListQuery() {
			return lstQuery;
		}
	}

	public static void evaluteModel() {

		// Take a session from the test.dat, fetch the queries, divide into
		// training(n-1:n) and test(1:n)
		// and build the session->vector of fragmentid's importance. Iterate
		// through all the sessions in database, build from MF.dat
		// and maintain list of top sessions for each test session.
		// Fetch fixed number of fragments for top sessions and fetch fixed
		// number of queries that have high importance of those fragments
		// Find the precision and recall by comparing the fragments from single
		// test query and queries fetched in the previous step

		try {
			Connection conn = ConnectionFactory.getConnection();

			int iNumOfTopSessions = Configuration.getmValForSession();
			List<Long> lstSessionId;

			double dSimilarity;

			String testfilePath;
			File oFileTest;
			FileReader fstreamTest = null;
			BufferedReader inTest = null;
			String sCurrentLine = null;
			String[] asSessionId = null;
			long lSessionId;
			String recommendedQueryIds = "";
			long testQueryId = 0;
			String testQueryFragmentImportance = "";
			String modelQueryFragmentImportance = "";

			HashMap<String, Double> hmTestSession_FragmentRank = null;
			HashMap<String, Double> hmModelSession_FragmentRank = null;
			TreeMap<Double, SessionGroup> tmTopSessions;
			HashSet<Long> setTopFragments = null;

			ModelDAO oModelDAO;

			String sql;
			ResultSet rs = null;
			PreparedStatement stmt = null;

			int iTrainingQueryCount = 0;

			// // System.out.println("Evaluation Controller");
			// System.out.println("Number of Folds: " + numOfFolds);
			int configurationId = Util.getNextConfigurationId();
			RunDataExcelWriter.createConfigurationFile(configurationId);
			for (int i = 1; i <= numOfFolds; i++) {
				double averageOfAvgPrecision = 0.0;
				double averageOfMaxPrecision = 0.0;
				double averageOfMaxRecall = 0.0;
				double averageOfAvgRecall = 0.0;

				RunDataExcelWriter excelWriter = new RunDataExcelWriter(
						configurationId, i);
				oModelDAO = new ModelDAO(i);

				testfilePath = testInputDir + "/fold" + i + "/test.dat";
				// System.out.println("Processing for fold" + i);
				oFileTest = new File(testfilePath);

				fstreamTest = new FileReader(testfilePath);
				inTest = new BufferedReader(fstreamTest);

				while ((sCurrentLine = inTest.readLine()) != null) {
					asSessionId = sCurrentLine.split(",");
				}

				// // System.out.println(asSessionId.length);

				lstSessionId = oModelDAO.getModelSessionIds();
				// Iterating through all the test sessions
				for (int j = 0; j < asSessionId.length; j++) {
					double globalMaxPrecision = 0.0;
					double globalAvgPrecision = 0.0;
					double globalAvgRecall = 0.0;
					double globalMaxRecall = 0.0;
					lSessionId = Long.parseLong(asSessionId[j]);
					// System.out.println("Processing for the session : "+lSessionId);
					hmTestSession_FragmentRank = new HashMap<String, Double>();
					tmTopSessions = new TreeMap<Double, SessionGroup>();
					setTopFragments = new HashSet<Long>();

					sql = "select count(*) as querycount from query where sessionid = ?";

					stmt = conn.prepareStatement(sql);
					stmt.setLong(1, lSessionId);
					rs = stmt.executeQuery();

					rs.next();
					iTrainingQueryCount = rs.getInt("querycount");

					sql = "select queryid, queryfragmentimportance from query where sessionid = ? ORDER BY queryid DESC";

					stmt = conn.prepareStatement(sql);
					stmt.setLong(1, lSessionId);
					rs = stmt.executeQuery();

					long[] aTestQueryIds = new long[iTrainingQueryCount];
					String[] asTestQueryFragmentImportance = new String[iTrainingQueryCount];
					int iTestQueries = 0;
					while (rs.next()) {

						aTestQueryIds[iTestQueries] = rs.getLong("queryid");
						asTestQueryFragmentImportance[iTestQueries++] = rs
								.getString("queryfragmentimportance");

					}

					for (int iteration = 0; iteration < iTrainingQueryCount; iteration++) {

						// For each query of test session, compute the fragment
						// importance and add it to the session fragment map
						for (int iteration1 = 0; iteration1 < iTrainingQueryCount; iteration1++) {

							if (iteration1 == iteration) {
								testQueryId = aTestQueryIds[iteration];
								testQueryFragmentImportance = asTestQueryFragmentImportance[iteration];
							} else {

								modelQueryFragmentImportance = asTestQueryFragmentImportance[iteration1];
								updateTestSession_FragmentRankMap(
										hmTestSession_FragmentRank,
										modelQueryFragmentImportance);
							}
						}
						Iterator<String> itString = hmTestSession_FragmentRank
								.keySet().iterator();
						String sSessionId = null;
						while (itString.hasNext()) {
							sSessionId = itString.next();
							hmTestSession_FragmentRank.put(sSessionId,
									hmTestSession_FragmentRank.get(sSessionId));
						}

						// // System.out.println("Session Id:" + lSessionId
						// +
						// " Training query count: " + iTrainingQueryCount);

						// Iterating through all the sessions in Model for
						// each
						// test session and collecting
						// top(iNumOfTopSessions)
						// similar sessions
						Iterator<Long> it = lstSessionId.iterator();
						long lModelSessionid;
						int iNoOfCurrentTopSessions = 0;
						SessionGroup oSessionGroup = null;
						Double oDouble = null;
						while (it.hasNext()) {

							lModelSessionid = it.next();
							hmModelSession_FragmentRank = oModelDAO
									.getFragmentMap(lModelSessionid);
							dSimilarity = Similarity.cosineSimilarity(
									hmModelSession_FragmentRank,
									hmTestSession_FragmentRank);

							if (iNoOfCurrentTopSessions >= iNumOfTopSessions) {
								if ((oDouble = tmTopSessions.firstKey()) < dSimilarity) {
									oSessionGroup = tmTopSessions
											.pollFirstEntry().getValue();

									if (oSessionGroup.getSize() > 1) {
										oSessionGroup.remove();
										tmTopSessions.put(oDouble,
												oSessionGroup);
									}

									if ((oSessionGroup = tmTopSessions
											.get(dSimilarity)) != null) {
										oSessionGroup
												.addSession(lModelSessionid);
									} else {
										tmTopSessions.put(dSimilarity,
												new SessionGroup(
														lModelSessionid));
									}
								}
							} else {
								if ((oSessionGroup = tmTopSessions
										.get(dSimilarity)) != null) {
									oSessionGroup.addSession(lModelSessionid);
								} else {
									tmTopSessions.put(dSimilarity,
											new SessionGroup(lModelSessionid));
								}
								iNoOfCurrentTopSessions++;
							}
						}

						// //
						// System.out.println("Count of top sessions for test session id "
						// + lSessionId + " : " + tmTopSessions.size());
						HashSet<Long> hsSessionId = new HashSet<Long>();
						Iterator<SessionGroup> itSessionGroup = tmTopSessions
								.values().iterator();
						HashSet<Long> oHashSet = null;
						while (itSessionGroup.hasNext()) {

							oSessionGroup = itSessionGroup.next();
							while (oSessionGroup.hasNext()) {
								Long sessionId = oSessionGroup.next();
								hsSessionId.add(sessionId);
								oHashSet = oModelDAO
										.getTopKFragmentMap(sessionId);

								for (Long fragmentId : oHashSet) {
									if (setTopFragments.contains(fragmentId)) {
										continue;
									} else {
										setTopFragments.add(fragmentId);
										break;
									}
								}

							}
							oHashSet = null;

						}
						String[] singleQueryFragImp;
						QueryDAO qDAO = DAOFactory.getQueryDAO();
						TreeMap<Double, QueryGroup> topKQueries = new TreeMap<Double, QueryGroup>();
						int nVal = Configuration.getnValForQuery();
						long fragmentid = 0;
						int numOfTopQueries = 0;
						QueryGroup queryGroup = null;
						// Parse list of session ids
						for (long sessionId : lstSessionId) {
							List<Query> queryList;

							queryList = qDAO.queryAll(sessionId);

							// Iterate through all queries of each training
							// session
							for (Query q : queryList) {
								singleQueryFragImp = q
										.getQueryfragmentimportance().split(
												"::");
								double importanceForQ = 0.0;
								int intersectionCount = 0;
								for (String importanceString : singleQueryFragImp) {
									fragmentid = Long
											.parseLong(importanceString
													.split(",")[0]);
									if (setTopFragments.contains(fragmentid)) {
										/*
										 * importanceForQ += Double
										 * .parseDouble(importanceString
										 * .split(",")[1]);
										 */
										intersectionCount++;
									}
								}
								int iSizeOfUnseenQuery = MetricsCalc
										.getFragmentList(
												testQueryFragmentImportance)
										.size();
								importanceForQ = (((double) intersectionCount) / iSizeOfUnseenQuery)
										* ((double) intersectionCount / numOfTopFragments);

								// push the final importance to tree.
								if (numOfTopQueries >= nVal) {
									if ((oDouble = topKQueries.firstKey()) < importanceForQ) {
										queryGroup = topKQueries
												.pollFirstEntry().getValue();

										if (queryGroup.getSize() > 1) {
											queryGroup.remove();
											topKQueries
													.put(oDouble, queryGroup);
										}

										if ((queryGroup = topKQueries
												.get(importanceForQ)) != null) {
											queryGroup.addQuery(q.getQueryid());
										} else {
											topKQueries.put(
													importanceForQ,
													new QueryGroup(q
															.getQueryid()));
										}
									}
								} else {
									if ((queryGroup = topKQueries
											.get(importanceForQ)) != null) {
										queryGroup.addQuery(q.getQueryid());
									} else {
										topKQueries.put(importanceForQ,
												new QueryGroup(q.getQueryid()));
									}
									numOfTopQueries++;
								}
							}
						}

						List<Long> listOfQueryIds = new ArrayList<Long>();
						for (QueryGroup qGroup : new ArrayList<QueryGroup>(
								topKQueries.values())) {
							listOfQueryIds.addAll(qGroup.getListQuery());
						}

						
						for (Long queryid : listOfQueryIds) {
							recommendedQueryIds += "" + queryid + ":";
						}
						MetricsCalc.calculatePrecisionAndRecall(listOfQueryIds,
								testQueryId);
						double averagePrecision = MetricsCalc
								.getAveragePrecision();
						double averageRecall = MetricsCalc.getAverageRecall();

						double maxPrecision = MetricsCalc.getMaxPrecision();
						double maxRecall = MetricsCalc.getMaxRecall();
						if (maxPrecision > globalMaxPrecision) {
							globalMaxPrecision = maxPrecision;
						}
						globalAvgPrecision += averagePrecision;
						if (maxRecall > globalMaxRecall) {
							globalMaxRecall = maxRecall;
						}

						globalAvgRecall += averageRecall;


					}
					PrecisionRecallStatistics.incrementPRStatistics(
							globalMaxPrecision, globalMaxRecall);
					averageOfAvgPrecision += (globalAvgPrecision / iTrainingQueryCount);
					averageOfAvgRecall += (globalAvgRecall/ iTrainingQueryCount);
					averageOfMaxPrecision += globalMaxPrecision;
					averageOfMaxRecall += globalMaxRecall;
					RunInfo runInfo = new RunInfo(lSessionId,
							globalAvgPrecision/iTrainingQueryCount, globalMaxPrecision, globalAvgRecall/iTrainingQueryCount,
							globalMaxRecall, testQueryId, recommendedQueryIds);

					excelWriter.writeRunInfoToExcel(runInfo);
				}
				averageOfAvgPrecision /= asSessionId.length;
				averageOfAvgRecall /= asSessionId.length;
				averageOfMaxPrecision /= asSessionId.length;
				averageOfMaxRecall /= asSessionId.length;

				excelWriter.insertBlankLines();
				excelWriter.insertBlankLines();
				excelWriter.writeCustomOutputToExcel("Average" + ","
						+ averageOfMaxPrecision + "," + averageOfAvgPrecision
						+ "," + averageOfMaxRecall + "," + averageOfAvgRecall);
				RunDataExcelWriter.writeStatistics(configurationId,
						new StatInfo("Run: " + i, averageOfAvgPrecision,
								averageOfMaxPrecision, averageOfAvgRecall,
								averageOfMaxRecall));
				excelWriter.closeAndFlush();
			}
			PrecisionRecallStatistics.persistDataInExcel(configurationId);
			PrecisionRecallStatistics.closeAndFlushStatWriter();

		} catch (Exception e) {
			e.printStackTrace();
		}
		RunDataExcelWriter.closeAndFlushStatWriter();
	}

	private static void updateTestSession_FragmentRankMap(
			HashMap<String, Double> hmTestSession_FragmentRank,
			String modelQueryFragmentImportance) {

		String[] asFragmentID_Importance = modelQueryFragmentImportance
				.split("::");
		String[] sFragmentID_Importance;

		String sFragmentId;
		double dImportance;
		Double oDouble;

		// // System.out.println("asFragmentID_Importance: " +
		// asFragmentID_Importance.length);

		for (String str : asFragmentID_Importance) {

			sFragmentID_Importance = str.split(",");
			sFragmentId = sFragmentID_Importance[0];
			dImportance = Double.parseDouble(sFragmentID_Importance[1]);

			if ((oDouble = hmTestSession_FragmentRank.get(sFragmentId)) != null) {
				dImportance += oDouble;
			}

			hmTestSession_FragmentRank.put(sFragmentId, dImportance);
		}
	}

}
